using System.Xml;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using WMM.Tapioca.Taxonomy;
using System.Xml.Linq;
using System.Linq;
using WMM.Tapioca.Utilities;
using System;

namespace WMM.Tapioca.Instance
{
	/// <summary>
    /// XBRL 2.1 Specification 4.8
    /// The unit element specifies the units in which a numeric item has been measured. 
    /// The content of the unit element MUST be either a simple unit of measure expressed with a single measure element or
    /// a ratio of products of units of measure, with the ratio represented by the divide element and the numerator and 
    /// denominator products both represented by a sequence of measure elements.
	/// </summary>
    public class Unit
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private XElement unitElement;

        /// <returns> Unit XElement. </returns>
        public XElement UnitElement
        {
            get { return unitElement; }
            set { unitElement = value; }
        }

        private List<XName> unitNumerators;
        private List<XName> unitDenominators;
        private DTS dts;

        private string id;

        /// <summary>
        /// The ID of this unit.
        /// </summary>
        public string Id
        {
            get { return id; }
            set { id = value; }
        }

		/// <summary>
        /// A collection of XName objects representing the set of measure qualified names for this unit.
		/// </summary>
		public List<XName> Measures
		{
			get;
			private set;
		}     

        /// <summary>
        /// Describes whether or not this unit represents a ratio. Returns true if this unit represents a ratio.
        /// Returns false if this unit does not represent a ratio.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is ratio; otherwise, <c>false</c>.
        /// </value>
		public bool IsRatio
		{
			get;
			private set;
		}

        /// <summary>
        /// Region information for this unit.
        /// </summary>
        public RegionInfo RegionInformation
        {
            get;
            private set;
        }

        /// <summary>
        /// Culture information for this unit.
        /// </summary>
        public CultureInfo CultureInformation
        {
            get;
            private set;
        }


        internal Unit(XElement unitElement, DTS dtsXbrl)
        {
            dts = dtsXbrl;
			//this.RegionInformation = null;
            this.unitElement = unitElement;
            id = unitElement.Attribute("id").Value;
            Measures = new List<XName>();
            IsRatio = false;
            unitNumerators = new List<XName>();
            unitDenominators = new List<XName>();

            foreach (XElement currentElement in unitElement.Elements())
            {
                string prefix = null;
                string measure = null;
                XNamespace ns = string.Empty; 

                // measure

                if (currentElement.Name.LocalName.Equals("measure"))
                {
                    GetMeasure(unitElement, currentElement, ref prefix, ref measure, ref ns);

                    Measures.Add(ns + measure);
                }

                //divide

                else if (currentElement.Name.LocalName.Equals("divide"))
                {
                    ProcessDivideElement(currentElement);
                    CheckForMeasuresCommonToNumeratorsAndDenominators();
                    IsRatio = true;
                }
            }
        }


        private static void GetMeasure(XElement unitElement, XElement currentElement, ref string prefix, ref string measure, ref XNamespace ns)
        {
            string[] textSplit = currentElement.Value.Split(':');

            if (textSplit.Length == 1)
            {
                prefix = string.Empty;
                measure = textSplit[0];
            }
            else
            {
                prefix = textSplit[0];
                measure = textSplit[1];
            }

            if (prefix.Length > 0)
            {
                ns = currentElement.GetNamespaceOfPrefix(prefix);
                if (ns.NamespaceName.Length == 0)
                {
                    ns = unitElement.GetNamespaceOfPrefix(prefix);
                }
                if (ns.NamespaceName.Length == 0)
                {
                    ns = unitElement.Parent.GetNamespaceOfPrefix(prefix);
                }
            }
            else
            {
                string nsDefault = unitElement.Parent.GetDefaultNamespace().NamespaceName;

                if (nsDefault.Equals(Constants.XBRLI))
                {
                    ns = nsDefault;
                }
            }
        }


        private void CheckForMeasuresCommonToNumeratorsAndDenominators()
        {
            foreach (XName currentNumeratorMeasure in unitNumerators)
            {
                if (unitDenominators.Contains(currentNumeratorMeasure))
                {
                    string messageFormat = AssemblyResources.GetName("UnitRatioUsesSameMeasureInNumeratorAndDenominator");
                    StringBuilder messageBuilder = new StringBuilder();
                    messageBuilder.AppendFormat(messageFormat, this.Id, currentNumeratorMeasure.ToString());
                    log.Error("Validation error on measures", new XbrlException(messageBuilder.ToString()));
                    dts.ValidatedInstance = false;
                }
            }
        }


        private void ProcessDivideElement(XElement divideElement)
        {
            foreach (XElement currentElement in divideElement.Elements())
            {
                if (currentElement.Name.LocalName.Equals("unitNumerator"))
                {
                    ProcessUnitNumerator(currentElement);
                }
                else if (currentElement.Name.LocalName.Equals("unitDenominator"))
                {
                    ProcessUnitDenominator(currentElement);
                }
            }
        }


        private void ProcessUnitNumerator(XElement unitDivideNumeratorElement)
        {
            foreach (XElement currentElement in unitDivideNumeratorElement.Elements())
            {
                string prefix = null;
                string measure = null;
                XNamespace ns = string.Empty;

                if (currentElement.Name.LocalName.Equals("measure"))
                {                    
                    GetMeasure(unitElement, currentElement, ref prefix, ref measure, ref ns);

                    unitNumerators.Add(ns + measure);
                }
            }
        }


        private void ProcessUnitDenominator(XElement unitDivideDenominatorElement)
        {
            foreach (XElement currentElement in unitDivideDenominatorElement.Elements())
            {
                string prefix = null;
                string measure = null;
                XNamespace ns = string.Empty; 

                if (currentElement.Name.LocalName.Equals("measure"))
                {
                    GetMeasure(unitElement, currentElement, ref prefix, ref measure, ref ns);

                    unitDenominators.Add(ns + measure);
                }
                    
            }
        }

        //------------------------------------------------------------------------------------
        //------------------------------------------------------------------------------------
        internal void SetCultureAndRegionInfoFromISO4217Code(string Iso4217Code)
        {
            //--------------------------------------------------------------------------------
            // See if any obsolete ISO 4217 codes are being used and support those separately.
            //--------------------------------------------------------------------------------
            if (Iso4217Code.Equals("DEM"))
            {
                SetCultureAndRegionInfoFromRegionInfoName("DE");
                return;
            }
            //--------------------------------------------------------------------------------
            // Get a list of all cultures and find one whose region information specifies the
            // given ISO 4217 code as its currency symbol.
            //--------------------------------------------------------------------------------
            CultureInfo[] AllSpecificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
            foreach (CultureInfo CurrentCultureInfo in AllSpecificCultures)
            {
                RegionInfo CurrentRegionInfo = new RegionInfo(CurrentCultureInfo.LCID);
                if (CurrentRegionInfo.ISOCurrencySymbol == Iso4217Code)
                {
                    this.CultureInformation = CurrentCultureInfo;
                    this.RegionInformation = CurrentRegionInfo;
                    return;
                }
            }
        }

        //------------------------------------------------------------------------------------
        // This method is a bit of a hack so that Tapioca passes unit test 304.24 in the
        // XBRL-CONF-CR3-2007-03-05 conformance suite. The XBRL document in 304.24 uses a unit
        // of measure called iso4217:DEM, which is an obsolete ISO 4217 currency code for the
        // German Mark. This has been replaced in favor of the Euro.
        //
        // This method searches for appropriate CultureInfo and RegionInfo settings given the
        // name of a region.
        //------------------------------------------------------------------------------------
        private void SetCultureAndRegionInfoFromRegionInfoName(string RegionInfoName)
        {
            CultureInfo[] AllSpecificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
            foreach (CultureInfo CurrentCultureInfo in AllSpecificCultures)
            {
                RegionInfo CurrentRegionInfo = new RegionInfo(CurrentCultureInfo.LCID);
                if (CurrentRegionInfo.Name == RegionInfoName)
                {
                    this.CultureInformation = CurrentCultureInfo;
                    this.RegionInformation = CurrentRegionInfo;
                    return;
                }
            }
        }

 
        internal bool StructureEquals(Unit otherUnit)
        {
            //normalize this thisUnitElement
            XElement thisUnitElementClone = new XElement(unitElement);
            thisUnitElementClone.Attribute("id").Remove();
            thisUnitElementClone.NormalizeElementsOrder();

            //normalize this OtherUnitthisUnitElement
            XElement otherUnitThisUnitElementClone = new XElement(otherUnit.unitElement);
            otherUnitThisUnitElementClone.Attribute("id").Remove();
            otherUnitThisUnitElementClone.NormalizeElementsOrder();

            if (unitElement == null || otherUnit.unitElement == null)
            {
                return false;
            }

            if (!Normalization.DeepEqualsWithNormalization(thisUnitElementClone, otherUnitThisUnitElementClone, false))
            {
                return false;
            }

            return true;
        }

    }
}
